package com.yunyao.framework.audit;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.util.DateUtils;
import com.aliyun.oss.ServiceException;
import com.yunyao.common.constant.enums.DateFormatPattern;
import com.yunyao.common.constant.enums.VarTypeEnums;
import com.yunyao.common.core.BaseContext;
import com.yunyao.common.core.UserInfoRequest;
import com.yunyao.common.core.redis.RedisCache;
import com.yunyao.common.dto.audit.AuditResultEnums;
import com.yunyao.common.dto.audit.CreateAuditFlowDto;
import com.yunyao.common.dto.audit.json.Assignment;
import com.yunyao.common.dto.audit.json.AuditTemplateConfig;
import com.yunyao.common.dto.audit.json.ChildProperties;
import com.yunyao.common.dto.audit.json.ChildShapes;
import com.yunyao.common.dto.audit.json.FKTableEM;
import com.yunyao.common.dto.audit.json.UserTaskAssignment;
import com.yunyao.common.dto.audit.xml.EndEvent;
import com.yunyao.common.dto.audit.xml.Process;
import com.yunyao.common.dto.audit.xml.ProcessDefinition;
import com.yunyao.common.dto.audit.xml.SequenceFlow;
import com.yunyao.common.dto.audit.xml.StartEvent;
import com.yunyao.common.dto.audit.xml.UserTask;
import com.yunyao.common.exception.AuditException;
import com.yunyao.common.utils.XmlToJsonConverter;
import com.yunyao.common.web.util.ConvertUtil;
import com.yunyao.dao.service.act.IActHiActinstService;

import com.yunyao.dao.model.*;

import com.yunyao.dao.service.act.*;
import com.yunyao.dao.service.system.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.yunyao.common.constant.Constants.MODEL_SCHEME;
import static com.yunyao.common.constant.Constants.MODEL_SCHEME_INFO;
import static com.yunyao.common.constant.Constants.PROCESS_INSTANCE_CODE;
import static com.yunyao.common.constant.Constants.PROCESS_INSTANCE_CODE_PREFIX;

/**
 * @author xingjishuai
 * @date 2024-08-06
 */
@Slf4j
@Component
public abstract class AbstractAuditFlowHandle {

    @Autowired
    protected RedisCache redisCache;
    @Autowired
    protected DefaultAuditModelInfo defaultAuditModelInfo;
    @Autowired
    protected IActHiIdentitylinkService iActHiIdentitylinkService;

    @Autowired
    protected IActHiProcinstService iActHiProcinstService;
    @Autowired
    protected IEstProcessStartRecordService iEstProcessStartRecordService;
    @Autowired
    protected IActHiVarinstService iActHiVarinstService;
    @Autowired
    protected IActHiActinstService iActHiActinstService;
    @Autowired
    protected IFTempFieldService ifTempFieldService;
    @Autowired
    protected IFFlowEndLogRecordService ifFlowEndLogRecordService;
    @Autowired
    protected IActReProcdefService iActReProcdefService;
    @Autowired
    private IActHiTaskinstService iActHiTaskinstService;
    @Autowired
    private IFModelSchemeService ifModelSchemeService;
    @Autowired
    private IFModelSchemeInfoService ifModelSchemeInfoService;
    @Autowired
    private IUserService iUserService;
    @Autowired
    private IActHiCommentService iActHiCommentService;

    // 定义常量
    private static final String USER_TASK_TYPE = "userTask";
    private static final String SEQUENCE_FLOW_TYPE = "sequenceFlow";
    private static final String USER_NAME_LIST = "usernamelist";
    private static final String START_EVENT_TYPE = "startEvent";
    private static final String END_EVENT_TYPE = "endEvent";
    private static final int REV_TASK_INST = 3;
    private static final int REV_ACT_INST = 1;
    private static final int REV_VAR_INST = 0;
    private static final String TEMP_TYPE_32 = "32";
    private static final String TEMP_TYPE_23 = "23";

    /**
     * 创建并启动流程实例
     *
     * @param modelId   模型ID
     * @param modelName 模型名称
     * @param bizId     业务ID
     * @param bizName   业务名称
     * @param bizStatus 业务状态
     * @return 包含流程实例信息的CreateAuditFlowDto对象
     * @throws Exception 如果操作失败，则抛出异常
     */
    protected CreateAuditFlowDto creatFlow(String modelId, String modelName, String bizId,
                                           String bizName, String bizStatus) throws Exception {
        // 获取当前操作用户信息
        UserInfoRequest userInfo = BaseContext.getUserInfo();
        // 获取模型方案信息
        FModelSchemeInfo modelSchemeInfo = getModelSchemeInfo(modelId);
        // 根据模型方案ID获取模型方案
        FModelScheme modelScheme = getModelScheme(modelSchemeInfo.getSchemeId());
        // 将模型XML内容转换为ProcessDefinition对象
        ProcessDefinition processDefinition = modelXmlToObject(modelScheme.getXmlContent());
        // 检查模型数据的合法性
        checkModelData(processDefinition);
        // 获取流程定义中的流程信息
        Process process = processDefinition.getProcess();
        // 获取部署ID
        String deploymentId = modelSchemeInfo.getDeploymentId();
        // 根据部署ID获取流程定义信息
        ActReProcdef proInfo = getProInfo(deploymentId);
        // 创建流程实例DTO对象，用于封装流程实例相关信息
        CreateAuditFlowDto createAuditFlowDto = new CreateAuditFlowDto();
        // 创建流程实例（历史流程实例表，存储流程实例历史数据（包含正在运行的流程实例）；）
        ActHiProcinst actHiProcinst = createActHiProcinst(proInfo, userInfo, process);
        // 设置流程实例ID
        String processInstanceId = actHiProcinst.getId();
        createAuditFlowDto.setProcessInstanceId(processInstanceId);
        // 设置部署ID
        createAuditFlowDto.setDeploymentId(deploymentId);
        // 设置模型方案信息ID
        createAuditFlowDto.setModelSchemeInfoId(modelId);
        // 设置流程版本号
        createAuditFlowDto.setProcessVersion(String.valueOf(proInfo.getVersion()));
        // 创建流程实例记录表
        createEstProcessStartRecord(bizId, bizName, bizStatus, modelName, processInstanceId);
        // 启动流程实例
        startFlow(processInstanceId, process, proInfo.getId(), deploymentId);
        // 返回包含流程实例信息的DTO对象
        return createAuditFlowDto;
    }


    /**
     * 执行流程实例
     *
     * @param processInstanceId 流程实例ID
     * @param deploymentId      部署ID
     * @param auditResult       审核结果
     * @param auditResultMsg    审核结果消息
     * @param attachList        附件列表
     * @return boolean 表示流程执行是否成功
     * @throws Exception 如果流程执行过程中发生错误，则抛出异常
     *                   <p>
     *                   此方法用于根据提供的流程实例ID和其他信息执行流程实例。它首先检查流程实例是否存在，
     *                   然后检查流程实例是否已完成。如果已完成，则不允许重复执行。接着，它从数据库中获取流程定义信息，
     *                   并将其转换为模型对象。然后，它解析流程定义并处理流程逻辑。
     */
    protected boolean executeFlow(String processInstanceId, String deploymentId, String auditResult,
                                  String auditResultMsg,
                                  String attachList) throws Exception {
        // 根据流程实例ID获取历史流程实例信息
        ActHiProcinst actHiProcinst =
                iActHiProcinstService.getByProcessInstanceId(processInstanceId);
        // 如果历史流程实例不存在，则抛出异常
        if (Objects.isNull(actHiProcinst)) {
            throw new AuditException("历史流程实例不存在");
        }
        // 获取流程实例的结束活动ID和结束时间
        String endActId = actHiProcinst.getEndActId();
        Date endTime = actHiProcinst.getEndTime();
        // 如果流程实例已完成，则抛出异常
        if (StringUtils.isNotBlank(endActId) && endTime != null) {
            throw new AuditException("历史流程实例已完成,不可重复执行");
        }
        // 通过部署ID获取模型方案信息
        FModelSchemeInfo modelSchemeInfo = getModelSchemeInfoByDeploymentId(deploymentId);
        // 获取模型方案
        FModelScheme modelScheme = getModelScheme(modelSchemeInfo.getSchemeId());
        // 获取流程定义信息
        ActReProcdef proInfo = getProInfo(deploymentId);
        // 将模型JSON内容转换为审核模板配置对象
        AuditTemplateConfig auditTemplateConfig = modelJsonToObject(modelScheme.getJsonContent());
        // 将模型XML内容转换为流程定义对象
        ProcessDefinition processDefinition = modelXmlToObject(modelScheme.getXmlContent());
        // 检查模型数据的完整性
        checkModelData(processDefinition);
        // 获取流程定义中的流程元素
        Process process = processDefinition.getProcess();
        // 获取流程中的所有序列流
        List<SequenceFlow> flowList = process.getSequenceFlow();
        // 获取流程中的所有用户任务
        List<UserTask> userTaskList = process.getUserTask();
        // 将序列流列表转换为Map，以便快速查找
        Map<String, SequenceFlow> sequenceFlowMap =
                flowList.stream().collect(Collectors.toMap(SequenceFlow::getId,
                        Function.identity(), (x, y) -> x));
        // 将用户任务列表转换为Map，以便快速查找
        Map<String, UserTask> userTaskMap =
                userTaskList.stream().collect(Collectors.toMap(UserTask::getId,
                        Function.identity(), (x, y) -> x));
        // 获取流程的起始事件
        StartEvent startEvent = process.getStartEvent();
        // 获取起始事件的ID
        String eventId = startEvent.getId();

        // 处理流程逻辑
        flowHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process, eventId, processInstanceId, auditResult, auditResultMsg, attachList, proInfo.getId(), deploymentId, true);
        // 检查是否存在结束事件，以确定流程是否成功执行
        ActHiActinst actHiActinst =
                iActHiActinstService.getByProcessInstanceIdAndType(processInstanceId, END_EVENT_TYPE);
        // 返回流程执行是否成功
        return Objects.nonNull(actHiActinst);
    }



    /**
     * 处理流程逻辑
     * 根据不同的事件类型调用相应的处理方法
     *
     * @param sequenceFlowMap     流序列映射
     * @param userTaskMap         用户任务映射
     * @param auditTemplateConfig 审批模板配置
     * @param process             流程定义
     * @param eventId             事件ID
     * @param processInstanceId   流程实例ID
     * @param auditResult         审批结果
     * @param auditResultMsg      审批结果消息
     * @param attachList          附件列表
     * @param procDefId           流程定义ID
     * @param deploymentId
     * @param isExecute           是否执行
     * @throws Exception 抛出异常
     */
    private void flowHandle(Map<String, SequenceFlow> sequenceFlowMap,
                            Map<String, UserTask> userTaskMap,
                            AuditTemplateConfig auditTemplateConfig,
                            Process process, String eventId, String processInstanceId, String auditResult, String auditResultMsg, String attachList, String procDefId, String deploymentId, boolean isExecute) throws Exception {
        // 获取审批模板配置中的子形状列表
        List<ChildShapes> childShapesList = auditTemplateConfig.getChildShapes();
        // 获取当前用户信息
        UserInfoRequest userInfo = BaseContext.getUserInfo();
        // 如果事件ID为空，则直接返回
        if (StringUtils.isBlank(eventId)) {
            return;
        }
        // 获取流程的开始事件
        StartEvent startEvent = process.getStartEvent();
        // 如果事件ID与开始事件的ID相同，则调用开始事件处理方法
        if (StringUtils.equals(eventId, startEvent.getId())) {
            startHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process, eventId,
                    processInstanceId, auditResult, auditResultMsg, attachList, procDefId, deploymentId, startEvent);
            return;
        }
        // 获取流程的结束事件
        EndEvent endEvent = process.getEndEvent();
        // 如果事件ID与结束事件的ID相同，则调用结束事件处理方法
        if (StringUtils.equals(eventId, endEvent.getId())) {
            endHandle(eventId, processInstanceId, procDefId, endEvent);
            return;
        }
        // 如果事件ID在流序列映射中存在，则调用流序列处理方法
        if (sequenceFlowMap.containsKey(eventId)) {
            sequenceHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process, eventId,
                    processInstanceId, auditResult, auditResultMsg, attachList, procDefId, deploymentId, isExecute);
            return;
        }
        // 如果事件ID在用户任务映射中存在，则调用用户任务处理方法
        if (userTaskMap.containsKey(eventId)) {
            userTaskHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process, eventId,
                    processInstanceId, auditResult, auditResultMsg, attachList, procDefId, isExecute, childShapesList, userInfo, deploymentId, endEvent);
            return;
        }
        // 如果以上条件都不满足，则抛出异常，表示流程id错误
        throw new AuditException("流程id错误,未查询所需执行的逻辑");
    }
    private static final String BTN_VALUE = "_btnValue";
    private static final String MACRO = "@MACRO@";
    /**
     * 处理用户任务
     *
     * @param sequenceFlowMap        流程线映射
     * @param userTaskMap            用户任务映射
     * @param auditTemplateConfig    审批模板配置
     * @param process                流程实例
     * @param eventId                事件ID
     * @param processInstanceId      流程实例ID
     * @param auditResult            审批结果
     * @param auditResultMsg         审批结果消息
     * @param attachList             附件列表
     * @param procDefId              流程定义ID
     * @param isExecute              是否执行
     * @param childShapesList        子形状列表
     * @param userInfo               用户信息
     * @param deploymentId           部署ID
     * @param endEvent               结束事件
     * @throws Exception             异常
     */
    private void userTaskHandle(Map<String, SequenceFlow> sequenceFlowMap,
                                Map<String, UserTask> userTaskMap,
                                AuditTemplateConfig auditTemplateConfig, Process process,
                                String eventId, String processInstanceId, String auditResult,
                                String auditResultMsg, String attachList, String procDefId,
                                boolean isExecute, List<ChildShapes> childShapesList,
                                UserInfoRequest userInfo, String deploymentId
                , EndEvent endEvent) throws Exception {
        // 根据事件ID获取用户任务
        UserTask userTask = userTaskMap.get(eventId);
        // 根据流程实例ID、事件ID和任务类型获取历史任务实例
        ActHiActinst actHiActinst = iActHiActinstService.getByProcessInstanceIdAndActIdAndType(processInstanceId, eventId, USER_TASK_TYPE);
        // 根据资源ID和事件ID获取子形状
        ChildShapes childShapes =
                childShapesList.stream().filter(x -> StringUtils.equals(x.getResourceId(),
                                eventId)).findFirst().orElse(null);
        // 检查子形状数据
        checkChildShapesData(childShapes);
        // 获取子形状属性
        ChildProperties childShapesProperties = childShapes.getProperties();
        // 获取用户任务分配配置
        UserTaskAssignment usertaskassignment = childShapesProperties.getUsertaskassignment();
        // 获取分配规则
        Assignment assignment = usertaskassignment.getAssignment();
        // 获取关联表信息
        List<FKTableEM> fkTableEMList = assignment.getFKtableEM();
        // 获取第一个关联表信息
        FKTableEM fkTableEM = fkTableEMList.stream().findFirst().orElse(null);
        // 获取对象类型
        String fObjType = fkTableEM.getF_ObjType();
        // 根据对象类型和用户信息获取用户ID
        String fUserId = StringUtils.equals(fObjType, "3") ? fkTableEM.getF_ObjId() :
                userInfo.getId();
        // 获取用户任务的输出流程线ID
        String outId = userTask.getOutgoing();
        // 如果历史任务实例为空，则初始化任务实例和任务
        if (Objects.isNull(actHiActinst)) {
            actHiActinst = initActInstAndTask(processInstanceId, procDefId, userTask, fUserId,isExecute);
        }
        // 如果需要执行任务
        if (isExecute) {
            // 构建任务完成的变量名
            String instName = userTask.getId() + BTN_VALUE;
            // 根据流程实例ID和变量名获取历史变量实例
            ActHiVarinst actHiVarinst = iActHiVarinstService.getByProcessInstanceIdAndName(processInstanceId, instName);
            // 如果历史变量实例为空且任务尚未结束，则进行任务完成的操作
            if (Objects.isNull(actHiVarinst) && actHiActinst.getEndTime() == null){
                // 如果审批结果不是同意，则走向结束事件的流程线
                if (!StringUtils.equals(auditResult, AuditResultEnums.AGREE.getKey())) {
                    outId = endEvent.getIncoming();
                }
                // 保存审批结果变量实例
                saveVarInst(processInstanceId, auditResult, instName,VarTypeEnums.STRING.getValue());
                // 保存临时字段
                saveTempFields(processInstanceId,userTask.getId(),userInfo.getId(),auditResult,
                        auditResultMsg,attachList,actHiActinst.getTaskId());
                // 设置任务结束时间
                actHiActinst.setEndTime(new Date());
                // 计算任务持续时间
                actHiActinst.setDuration(actHiActinst.getEndTime().getTime() - actHiActinst.getStartTime().getTime());
                // 设置任务办理人
                actHiActinst.setAssignee(userInfo.getId());
                // 更新历史任务实例
                iActHiActinstService.updateById(actHiActinst);
                // 根据流程实例ID和任务定义Key更新任务实例信息
                iActHiTaskinstService.updateInfoByProcInstIdAndTaskDefKey(processInstanceId,
                        userTask.getId(), actHiActinst.getTaskId(),userInfo.getId());
                // 结束更新变量实例
                iActHiVarinstService.endUpdate(processInstanceId, USER_NAME_LIST,
                        VarTypeEnums.SERIALIZABLE.getValue());
                // 标记任务已结束，不再执行后续流程
                isExecute = false;
            }
            // 根据流程线映射、用户任务映射等信息处理流程
            flowHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process, outId,
                    processInstanceId, auditResult, auditResultMsg, attachList, procDefId,
                    deploymentId, isExecute);
        }
    }
    /**
     * 获取临时字段数据
     * 此方法用于组装与流程实例、用户、审批结果等相关的临时字段数据，并将其存储在一个Map中
     * 它还负责将审批信息作为评论添加到流程实例中
     *
     * @param processInstanceId 流程实例ID，用于标识流程实例
     * @param userId 用户ID，执行操作的用户标识
     * @param auditResult 审批结果，用于记录审批决策
     * @param auditResultMsg 审批结果消息，用于记录审批的详细信息
     * @param taskId 任务ID，当前操作的任务标识
     * @param attachList 附件列表，与任务相关的附件信息
     * @return 返回包含临时字段数据的Map对象
     */
    private Map<String, String> getTempFieldData(String processInstanceId, String userId, String auditResult,
                                                 String auditResultMsg, String taskId, String attachList) {
        // 创建一个HashMap对象来存储临时字段数据
        HashMap<String, String> tempValueMap = new HashMap<>();
        // 将审批结果作为按钮值存储
        tempValueMap.put("btnValue", auditResult);
        // 将审批结果消息存储
        tempValueMap.put("message", auditResultMsg);
        // 将审批信息作为评论创建在流程实例中
        createComment(processInstanceId, taskId, userId, JSON.toJSONString(tempValueMap));
        // 将任务ID存储
        tempValueMap.put("taskId", taskId);
        // 存储一个空字符串作为变量的占位符
        tempValueMap.put("variables", "");
        // 存储一个空字符串作为拒绝节点ID的占位符
        tempValueMap.put("rejectNodeId", "");
        // 将附件列表存储
        tempValueMap.put("attachList", attachList);
        // 返回包含所有临时字段数据的Map对象
        return tempValueMap;
    }
    /**
     * 保存临时字段信息
     * 该方法主要用于将流程实例ID、用户任务ID、用户ID、审核结果等信息
     * 封装成临时字段对象并保存，以便在流程审计过程中使用
     *
     * @param processInstanceId 流程实例ID，用于追踪流程的唯一标识
     * @param userTaskId 用户任务ID，标识流程中的一个具体任务
     * @param userId 用户ID，执行任务的用户标识
     * @param auditResult 审核结果，表示任务审核的结果
     * @param auditResultMsg 审核结果消息，具体描述审核通过或不通过的原因
     * @param attachList 附件列表，任务相关的附件信息
     * @param taskId 任务ID，标识具体任务的唯一标识
     */
    private void saveTempFields(String processInstanceId, String userTaskId, String userId,
                                    String auditResult, String auditResultMsg, String attachList,
                                    String taskId) {
        // 构建临时字段的键值，用于唯一标识一个流程实例中的特定任务
        String key = processInstanceId + MACRO + userTaskId;
        // 创建临时字段对象，类型为32，用于存储详细的审核信息
        FTempField fTempField = createTempField(key, TEMP_TYPE_32,
                getTempFieldData(processInstanceId,userId,auditResult,
                auditResultMsg, taskId,  attachList));
        // 创建另一个临时字段对象，类型为23，用于存储与用户相关的审核结果
        FTempField fTempFieldUser = createTempField(key + MACRO + userId, TEMP_TYPE_23, auditResult);

        try {
            // 尝试保存临时字段对象到数据库
            ifTempFieldService.save(fTempField);
            // 保存与用户相关的临时字段对象到数据库
            ifTempFieldService.save(fTempFieldUser);
        } catch (Exception e) {
            // 如果保存失败，抛出服务异常，提供错误信息
            throw new ServiceException("Failed to save temporary fields.", e);
        }
    }

    /**
     * 创建临时字段对象
     * 该方法用于将键、临时类型和值封装到FTempField对象中，便于后续处理
     * 主要用于解决在特定场景下需要临时存储数据的需求
     *
     * @param key 临时字段的键，用于唯一标识字段
     * @param tempType 临时字段的类型，决定了字段的处理方式
     * @param value 临时字段的值，可以是任意类型，会被转换为JSON字符串存储
     * @return 返回创建的FTempField对象
     */
    private FTempField createTempField(String key, String tempType, Object value) {
        FTempField fTempField = new FTempField();
        fTempField.setTempKey(key);
        fTempField.setTempType(tempType);
        fTempField.setTempValue(JSON.toJSONString(value));
        return fTempField;
    }

    private void saveVarInst(String processInstanceId, String auditResult, String name,
                             String varType) {
        ActHiVarinst nowActHiVarInst = new ActHiVarinst();
        nowActHiVarInst.setRev(REV_VAR_INST);
        nowActHiVarInst.setProcInstId(processInstanceId);
        nowActHiVarInst.setExecutionId(processInstanceId);
        nowActHiVarInst.setName(name);
        nowActHiVarInst.setVarType(varType);
        nowActHiVarInst.setText(auditResult);
        if (VarTypeEnums.SERIALIZABLE.getValue().equals(varType)){
            nowActHiVarInst.setBytearrayId(processInstanceId);
        }
        nowActHiVarInst.setCreateTime(new Date());
        nowActHiVarInst.setLastUpdatedTime(new Date());
        iActHiVarinstService.save(nowActHiVarInst);
    }

    private ActHiActinst initActInstAndTask(String processInstanceId, String procDefId, UserTask userTask, String fUserId, boolean isExecute) {
        ActHiTaskinst actHiTaskinst = new ActHiTaskinst();
        actHiTaskinst.setProcDefId(procDefId);
        actHiTaskinst.setProcInstId(processInstanceId);
        actHiTaskinst.setRev(REV_TASK_INST);
        actHiTaskinst.setTaskDefKey(userTask.getId());
        actHiTaskinst.setName(userTask.getName());
        actHiTaskinst.setAssignee(fUserId);
        actHiTaskinst.setStartTime(new Date());
        actHiTaskinst.setPriority(50);
        actHiTaskinst.setLastUpdatedTime(new Date());
        iActHiTaskinstService.save(actHiTaskinst);
        ActHiActinst actHiActinst = new ActHiActinst();
        actHiActinst.setRev(REV_ACT_INST);
        actHiActinst.setProcDefId(procDefId);
        actHiActinst.setProcInstId(processInstanceId);
        actHiActinst.setExecutionId(processInstanceId);
        actHiActinst.setActId(userTask.getId());
        actHiActinst.setActName(userTask.getName());
        actHiActinst.setTaskId(actHiTaskinst.getId());
        actHiActinst.setActType(USER_TASK_TYPE);
        actHiActinst.setStartTime(new Date());
        actHiActinst.setAssignee(fUserId);
        iActHiActinstService.save(actHiActinst);
        if (isExecute){
            adminAddUserLink(processInstanceId, actHiTaskinst.getId());
            adminDeleteUserLink(processInstanceId, actHiTaskinst.getId());
        }
        createAddUserLink(processInstanceId, actHiTaskinst.getId(), fUserId);
        return actHiActinst;
    }

    /**
     * 处理流程定义中的流程
     *
     * @param sequenceFlowMap     流程定义与流程对象的映射
     * @param userTaskMap         用户任务定义与用户任务对象的映射
     * @param auditTemplateConfig 审核模板配置
     * @param process             流程对象
     * @param eventId             事件ID
     * @param processInstanceId   流程实例ID
     * @param auditResult         审核结果
     * @param auditResultMsg      审核结果消息
     * @param attachList          附件列表
     * @param procDefId           流程定义ID
     * @param deploymentId
     * @param isExecute           是否执行
     * @throws Exception 抛出异常
     */
    private void sequenceHandle(Map<String, SequenceFlow> sequenceFlowMap,
                                Map<String, UserTask> userTaskMap,
                                AuditTemplateConfig auditTemplateConfig, Process process,
                                String eventId, String processInstanceId, String auditResult,
                                String auditResultMsg, String attachList, String procDefId, String deploymentId, boolean isExecute) throws Exception {
        // 根据事件ID获取流程定义
        SequenceFlow sequenceFlow = sequenceFlowMap.get(eventId);
        // 查询流程实例和活动类型的关联信息
        ActHiActinst actHiActinst =
                iActHiActinstService.getByProcessInstanceIdAndActIdAndType(processInstanceId,
                        eventId, SEQUENCE_FLOW_TYPE);
        // 如果关联信息已存在，则无需重复处理，直接返回
        if (Objects.isNull(actHiActinst)) {
            // 创建新的流程实例和活动类型的关联信息对象
            ActHiActinst sequenceFlowActHiActinst = new ActHiActinst();
            sequenceFlowActHiActinst.setRev(1);
            sequenceFlowActHiActinst.setProcDefId(procDefId);
            sequenceFlowActHiActinst.setProcInstId(processInstanceId);
            sequenceFlowActHiActinst.setExecutionId(processInstanceId);
            sequenceFlowActHiActinst.setActId(sequenceFlow.getId());
            sequenceFlowActHiActinst.setActType(SEQUENCE_FLOW_TYPE);
            sequenceFlowActHiActinst.setStartTime(new Date());
            sequenceFlowActHiActinst.setEndTime(new Date());
            // 计算流程处理时间
            sequenceFlowActHiActinst.setDuration(sequenceFlowActHiActinst.getEndTime().getTime() - sequenceFlowActHiActinst.getStartTime().getTime());
            // 保存流程实例和活动类型的关联信息
            iActHiActinstService.save(sequenceFlowActHiActinst);
        }
        // 继续处理下一个流程节点
        flowHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process,
                sequenceFlow.getTargetRef(), processInstanceId, auditResult, auditResultMsg, attachList, procDefId, deploymentId, isExecute);
    }



    /**
     * 开始处理流程实例
     * 本方法主要负责在流程定义中查找并处理开始事件，同时记录相关的审批信息
     * 如果开始事件已经处理过，则按照流程定义推进流程；否则，初始化流程实例
     *
     * @param sequenceFlowMap     流转关系映射
     * @param userTaskMap         用户任务映射
     * @param auditTemplateConfig 审批模板配置
     * @param process             流程定义
     * @param eventId             事件ID
     * @param processInstanceId   流程实例ID
     * @param auditResult         审批结果
     * @param auditResultMsg      审批结果消息
     * @param attachList          附件列表
     * @param procDefId           流程定义ID
     * @param deploymentId
     * @param startEvent          开始事件
     * @throws Exception 处理过程中可能抛出的异常
     */
    private void startHandle(Map<String, SequenceFlow> sequenceFlowMap,
                             Map<String, UserTask> userTaskMap,
                             AuditTemplateConfig auditTemplateConfig, Process process,
                             String eventId, String processInstanceId, String auditResult,
                             String auditResultMsg, String attachList, String procDefId, String deploymentId, StartEvent startEvent) throws Exception {
        // 查询流程实例中指定活动的执行历史，以确认开始事件是否已处理
        ActHiActinst actHiActinst =
                iActHiActinstService.getByProcessInstanceIdAndActIdAndType(processInstanceId,
                        eventId, START_EVENT_TYPE);
        if (Objects.nonNull(actHiActinst)) {
            // 如果开始事件已处理，根据流程定义推进流程，并记录审批信息
            flowHandle(sequenceFlowMap, userTaskMap, auditTemplateConfig, process,
                    startEvent.getOutgoing(), processInstanceId, auditResult, auditResultMsg, attachList, procDefId, deploymentId, true);
            return;
        }
        // 如果开始事件未处理，初始化流程实例
        startFlow(processInstanceId, process, procDefId, deploymentId);
    }

    /**
     * 处理流程结束事件
     *
     * @param eventId           结束事件的ID
     * @param processInstanceId 流程实例的ID
     * @param procDefId         流程定义的ID
     * @param endEvent          结束事件的实例
     *                          <p>
     *                          说明：本函数负责记录流程结束的相关信息，并更新流程历史数据
     */
    private void endHandle(String eventId, String processInstanceId, String procDefId, EndEvent endEvent) {
        // 查询流程历史活动实例表中是否已存在对应的结束事件记录
        ActHiActinst actHiActinst =
                iActHiActinstService.getByProcessInstanceIdAndActIdAndType(processInstanceId,
                        eventId, END_EVENT_TYPE);
        // 如果结束事件记录已存在，则无需重复记录，直接返回
        if (Objects.nonNull(actHiActinst)) {
            return;
        }
        // 创建一个新的历史活动实例对象，用于记录结束事件
        ActHiActinst endActHiActinst = new ActHiActinst();
        endActHiActinst.setRev(REV_ACT_INST); // 设置版本号为1
        endActHiActinst.setProcDefId(procDefId); // 设置流程定义ID
        endActHiActinst.setProcInstId(processInstanceId); // 设置流程实例ID
        endActHiActinst.setExecutionId(processInstanceId); // 设置执行ID，此处与流程实例ID相同
        endActHiActinst.setActId(endEvent.getId()); // 设置活动ID
        endActHiActinst.setActName(endEvent.getName()); // 设置活动名称
        endActHiActinst.setActType(END_EVENT_TYPE); // 设置活动类型为结束事件
        endActHiActinst.setStartTime(new Date()); // 设置开始时间为当前时间
        endActHiActinst.setEndTime(new Date()); // 设置结束时间为当前时间
        // 计算持续时间（以毫秒为单位）
        endActHiActinst.setDuration(endActHiActinst.getEndTime().getTime() - endActHiActinst.getStartTime().getTime());
        // 保存结束事件的历史记录
        iActHiActinstService.save(endActHiActinst);
        // 更新流程变量历史，指示流程结束
        iActHiVarinstService.endUpdate(processInstanceId, USER_NAME_LIST,
                VarTypeEnums.SERIALIZABLE.getValue());
        // 更新流程实例历史，设置结束事件ID
        iActHiProcinstService.endUpdate(processInstanceId, endEvent.getId());
        // 创建流程结束日志
        createFlowEndLog(processInstanceId);
    }


    /**
     * 判断是否需要审核（当前用户）
     *
     * @param schemeInfoId
     * @param processInstanceId
     * @return
     * @throws Exception
     */
    protected boolean isCheckAudit(String schemeInfoId, String processInstanceId) {
        try {
            return checkAuditHandle(schemeInfoId, processInstanceId);
        } catch (Exception var) {
            log.error("判断是否需要审核（当前用户）异常", var);
            return false;
        }

    }

    /**
     * 检查用户是否具有审核权限
     * 该方法通过解析流程定义和审核模板配置来判断当前用户是否具备对特定流程实例的审核权限
     *
     * @param schemeInfoId      流程方案信息ID
     * @param processInstanceId 流程实例ID
     * @return 如果用户具有审核权限返回true，否则返回false
     * @throws Exception 如果出现异常情况，将抛出异常
     */
    private boolean checkAuditHandle(String schemeInfoId, String processInstanceId) throws Exception {
        // 获取当前操作的用户ID
        String userId = BaseContext.getUserId();
        // 检查用户ID、流程方案信息ID和流程实例ID是否为空，任一为空则返回false
        if (StringUtils.isBlank(userId) || StringUtils.isBlank(schemeInfoId) || StringUtils.isBlank(processInstanceId)) {
            return false;
        }
        // 根据流程方案信息ID获取流程方案信息
        FModelSchemeInfo modelSchemeInfo = getModelSchemeInfo(schemeInfoId);
        // 如果流程方案信息为空，则返回false
        if (Objects.isNull(modelSchemeInfo)) {
            return false;
        }
        // 获取流程方案的ID
        String schemeId = modelSchemeInfo.getSchemeId();
        // 根据流程方案ID获取流程方案
        FModelScheme modelScheme = getModelScheme(schemeId);
        // 如果流程方案为空，则返回false
        if (Objects.isNull(modelScheme)) {
            return false;
        }
        // 将流程方案的XML内容转换为流程定义对象
        ProcessDefinition processDefinition = modelXmlToObject(modelScheme.getXmlContent());
        // 如果流程定义为空，则返回false
        if (Objects.isNull(processDefinition)) {
            return false;
        }
        // 将流程方案的JSON内容转换为审核模板配置对象
        AuditTemplateConfig auditTemplateConfig = modelJsonToObject(modelScheme.getJsonContent());
        // 如果审核模板配置为空，则返回false
        if (Objects.isNull(auditTemplateConfig)) {
            return false;
        }
        // 获取审核模板配置中的子形状列表
        List<ChildShapes> childShapesList = auditTemplateConfig.getChildShapes();
        // 如果子形状列表为空，则返回false
        if (CollectionUtils.isEmpty(childShapesList)) {
            return false;
        }

        // 获取流程定义中的流程对象
        Process process = processDefinition.getProcess();
        // 如果流程对象为空，则返回false
        if (Objects.isNull(process)) {
            return false;
        }
        // 获取流程中的用户任务列表
        List<UserTask> userTaskList = process.getUserTask();
        // 如果用户任务列表为空，则返回false
        if (CollectionUtils.isEmpty(userTaskList)) {
            return false;
        }
        // 获取流程的开始事件
        StartEvent startEvent = process.getStartEvent();
        // 如果开始事件为空，则返回false
        if (Objects.isNull(startEvent)) {
            return false;
        }
        // 获取流程的结束事件
        EndEvent endEvent = process.getEndEvent();
        // 如果结束事件为空，则返回false
        if (Objects.isNull(endEvent)) {
            return false;
        }
        // 根据流程实例ID获取历史任务实例列表
        List<ActHiTaskinst> actHiTaskinstList =
                iActHiTaskinstService.listByProcessInstanceId(processInstanceId);
        // 如果历史任务实例列表为空，则返回false
        if (CollectionUtils.isEmpty(actHiTaskinstList)) {
            return false;
        }
        // 获取开始事件的流出线
        String outgoing = startEvent.getOutgoing();
        // 将用户任务列表转换为ConcurrentMap，以便快速查找
        ConcurrentMap<String, UserTask> userTaskConcurrentMap =
                userTaskList.stream().collect(Collectors.toConcurrentMap(UserTask::getIncoming,
                        Function.identity(), (a, b) -> b));
        // 过滤已完成的历史任务实例，并获取任务定义键列表
        List<String> list =
                actHiTaskinstList.stream().filter(x -> Objects.nonNull(x.getEndTime())).map(ActHiTaskinst::getTaskDefKey).toList();
        // 处理用户任务和流程事件，计算当前流程的关键字
        String processKey = processHandle(userTaskConcurrentMap, outgoing, endEvent, list);
        // 如果流程关键字与结束事件的ID相同，则返回false
        if (StringUtils.equals(endEvent.getId(), processKey)) {
            return false;
        }
        // 检查当前流程的关键字是否在用户控制列表中，如果在则返回true，否则返回false
        List<User> userAdminList = iUserService.listUserAdministrator();
        List<String> userAdminIdList = List.of();
        if (CollectionUtils.isNotEmpty(userAdminList)) {
            userAdminIdList = userAdminList.stream().map(User::getId).toList();
        }
        boolean isAdmin =
                CollectionUtils.isNotEmpty(userAdminIdList) && userAdminIdList.contains(userId);
        // 检查用户控制列表是否为空，为空则返回false
        List<String> userControlList = checkUserControl(childShapesList);
        boolean isUser = CollectionUtils.isNotEmpty(userControlList) && userControlList.contains(processKey);
        return isAdmin || isUser;
    }

    /**
     * 检查用户控制列表
     * <p>
     * 此方法接收一个ChildShapes对象列表作为参数，从中提取并检查控制ID，
     * 并将这些ID保存在一个字符串列表中返回
     *
     * @param childShapesList 子形状列表，包含要检查的控制信息
     * @return 返回一个包含所有检查过的控制ID的字符串列表
     */
    private List<String> checkUserControl(List<ChildShapes> childShapesList) {
        // 初始化一个字符串列表来存储控制ID
        List<String> controlIdList = Lists.newArrayList();
        // 遍历子形状列表
        for (ChildShapes childShapes : childShapesList) {
            // 检查子数据，并将控制ID添加到列表中
            checkChildData(childShapes, controlIdList);
        }
        // 返回存储了所有控制ID的列表
        return controlIdList;
    }

    /**
     * 检查子形状的数据是否符合用户任务分配
     * 如果数据有效，将子形状的资源ID添加到控制ID列表中
     *
     * @param childShapes   子形状对象，包含形状属性和任务分配信息
     * @param controlIdList 控制ID列表，用于存储通过检查的子形状的资源ID
     */
    private void checkChildData(ChildShapes childShapes, List<String> controlIdList) {
        // 获取当前用户ID
        String userId = BaseContext.getUserId();

        // 获取子形状的属性
        ChildProperties properties = childShapes.getProperties();
        // 如果属性为空，则返回
        if (Objects.isNull(properties)) {
            return;
        }

        // 获取用户任务分配信息
        UserTaskAssignment usertaskassignment = properties.getUsertaskassignment();
        // 如果用户任务分配信息为空，则返回
        if (Objects.isNull(usertaskassignment)) {
            return;
        }

        // 获取分配对象
        Assignment assignment = usertaskassignment.getAssignment();
        // 如果分配对象为空，则返回
        if (Objects.isNull(assignment)) {
            return;
        }

        // 获取与分配对象关联的表格列表
        List<FKTableEM> tabelList = assignment.getFKtableEM();
        // 如果表格列表为空，则返回
        if (CollectionUtils.isEmpty(tabelList)) {
            return;
        }

        // 检查表格列表中是否存在与当前用户ID匹配的项
        boolean anyMatch = tabelList.stream().anyMatch(x -> StringUtils.equals(x.getF_ObjId(),
                userId));
        // 如果没有匹配项，则返回
        if (!anyMatch) {
            return;
        }

        // 如果子形状的数据有效，将其资源ID添加到控制ID列表中
        controlIdList.add(childShapes.getResourceId());
    }


    /**
     * 处理任务流程
     *
     * @param userTaskConcurrentMap 用户任务的并发映射，映射关系为流程id到用户任务
     * @param outgoing              当前流程节点的输出流程id
     * @param endEvent              流程结束事件对象，包含结束事件的信息
     * @param idList                已处理的任务id列表
     * @return 处理后的任务id
     * @throws AuditException 在流程id错误或流程定义错误时抛出
     */
    private String processHandle(ConcurrentMap<String, UserTask> userTaskConcurrentMap,
                                 String outgoing, EndEvent endEvent, List<String> idList) throws AuditException {
        // 检查输出流程id是否有效
        if (StringUtils.isBlank(outgoing)) {
            throw new AuditException("流程id错误");
        }
        // 获取当前流程节点的输入流程id
        String incoming = endEvent.getIncoming();
        // 如果输出流程id与输入流程id相同，则表示流程结束
        if (StringUtils.equals(outgoing, incoming)) {
            return endEvent.getId();
        }
        // 从映射中获取与输出流程id对应的用户任务
        UserTask userTask = userTaskConcurrentMap.get(outgoing);
        // 如果找不到对应的用户任务，则抛出流程定义错误异常
        if (Objects.isNull(userTask)) {
            throw new AuditException("流程定义错误");
        }
        // 检查任务id是否已在已处理的任务列表中
        if (idList.contains(userTask.getId())) {
            // 如果已处理，则递归处理当前任务的输出流程
            processHandle(userTaskConcurrentMap, userTask.getOutgoing(), endEvent, idList);
        }
        // 返回当前处理的任务id
        return userTask.getId();
    }


    /**
     * 检查子形状数据
     *
     * @param childShapes 子形状对象，包含流程的配置信息
     * @throws AuditException 如果子形状数据或其属性信息为空，或配置信息异常
     */
    private void checkChildShapesData(ChildShapes childShapes) throws AuditException {
        // 检查子形状对象是否为空
        if (Objects.isNull(childShapes)) {
            throw new AuditException("流程id错误,未查询所需执行的配置信息");
        }
        // 获取子形状的属性信息
        ChildProperties childShapesProperties = childShapes.getProperties();
        // 检查属性信息是否为空
        if (Objects.isNull(childShapesProperties)) {
            throw new AuditException("childShapesProperties : 配置信息异常");
        }
        // 获取用户任务分配信息
        UserTaskAssignment usertaskassignment = childShapesProperties.getUsertaskassignment();
        // 检查用户任务分配信息是否为空
        if (Objects.isNull(usertaskassignment)) {
            throw new AuditException("usertaskassignment : 配置信息异常");
        }
        // 获取分配信息
        Assignment assignment = usertaskassignment.getAssignment();
        // 检查分配信息是否为空
        if (Objects.isNull(assignment)) {
            throw new AuditException("assignment : 配置信息异常");
        }
        // 获取外键表信息列表
        List<FKTableEM> fkTableEMList = assignment.getFKtableEM();
        // 检查外键表信息列表是否为空
        if (CollectionUtils.isEmpty(fkTableEMList)) {
            throw new AuditException("fkTableEMList : 配置信息异常");
        }
        // 从外键表信息列表中获取第一个元素
        FKTableEM fkTableEM = fkTableEMList.stream().findFirst().orElse(null);
        // 检查获取到的外键表信息是否为空
        if (Objects.isNull(fkTableEM)) {
            throw new AuditException("fkTableEMList: findFirst : 配置信息异常");
        }
    }


    private void createComment(String processInstanceId, String taskId, String fUserId,
                               String msg) {
        String userId = BaseContext.getUserId();
        ActHiComment comment = new ActHiComment();
        comment.setType("comment");
        comment.setUserId(fUserId);
        comment.setTime(new Date());
        comment.setTaskId(taskId);
        comment.setProcInstId(processInstanceId);
        comment.setAction("AddComment");
        comment.setMessage(msg);
        iActHiCommentService.save(comment);
        ActHiComment actHiComment = new ActHiComment();
        actHiComment.setType("event");
        actHiComment.setUserId(userId);
        actHiComment.setTime(new Date());
        actHiComment.setTaskId(taskId);
        actHiComment.setAction("AddUserLink");
        actHiComment.setMessage(fUserId + "_|_assignee");
        iActHiCommentService.save(actHiComment);
    }

    private void createAddUserLink(String processInstanceId, String taskId, String fUserId) {
        String userId = BaseContext.getUserId();
        ActHiComment actHiComment = new ActHiComment();
        ActHiIdentitylink actHiIdentitylink = new ActHiIdentitylink();
        actHiIdentitylink.setType("participant");
        actHiIdentitylink.setUserId(fUserId);
        actHiIdentitylink.setCreateTime(new Date());
        actHiIdentitylink.setProcInstId(processInstanceId);
        actHiComment.setType("event");
        actHiComment.setUserId(userId);
        actHiComment.setTime(new Date());
        actHiComment.setTaskId(taskId);
        actHiComment.setAction("AddUserLink");
        actHiComment.setMessage(fUserId + "_|_candidate");
        iActHiCommentService.save(actHiComment);
        iActHiIdentitylinkService.save(actHiIdentitylink);
    }

    private void adminAddUserLink(String processInstanceId, String taskId) {
        String userId = BaseContext.getUserId();
        List<User> users = iUserService.listUserAdministrator();
        if (CollectionUtils.isEmpty(users)) {
            return;
        }
        ArrayList<ActHiComment> commentList = Lists.newArrayList();
        ArrayList<ActHiIdentitylink> identitylinkList = Lists.newArrayList();
        for (User user : users) {
            ActHiComment actHiComment = new ActHiComment();
            ActHiIdentitylink actHiIdentitylink = new ActHiIdentitylink();
            actHiIdentitylink.setType("participant");
            actHiIdentitylink.setUserId(user.getId());
            actHiIdentitylink.setCreateTime(new Date());
            actHiIdentitylink.setProcInstId(processInstanceId);
            actHiComment.setType("event");
            actHiComment.setUserId(userId);
            actHiComment.setTime(new Date());
            actHiComment.setTaskId(taskId);
            actHiComment.setAction("AddUserLink");
            actHiComment.setMessage(user.getId() + "_|_candidate");
            commentList.add(actHiComment);
            identitylinkList.add(actHiIdentitylink);
        }
        iActHiCommentService.saveBatch(commentList);
        iActHiIdentitylinkService.saveBatch(identitylinkList);
    }

    private void adminDeleteUserLink(String processInstanceId, String taskId) {
        String userId = BaseContext.getUserId();
        List<User> users = iUserService.listUserAdministrator();
        if (CollectionUtils.isEmpty(users)) {
            return;
        }
        ArrayList<ActHiComment> commentList = Lists.newArrayList();
        for (User user : users) {
            ActHiComment actHiComment = new ActHiComment();
            actHiComment.setType("event");
            actHiComment.setUserId(userId);
            actHiComment.setTime(new Date());
            actHiComment.setTaskId(taskId);
            actHiComment.setAction("DeleteUserLink");
            actHiComment.setMessage(user.getId() + "_|_candidate");
            commentList.add(actHiComment);
        }
        iActHiCommentService.saveBatch(commentList);
    }


    /**
     * 启动流程实例
     *
     * @param processInstanceId 流程实例ID
     * @param process 流程对象
     * @param procDefId 流程定义ID
     * @param deploymentId 部署ID
     * @throws Exception 异常
     */
    private void startFlow(String processInstanceId, Process process, String procDefId,
                           String deploymentId) throws Exception {
        // 获取当前用户信息
        UserInfoRequest userInfo = BaseContext.getUserInfo();
        // 获取流程的开始事件
        StartEvent startEvent = process.getStartEvent();
        // 初始化历史变量实例列表
        List<ActHiVarinst> actHiVarinstList = Lists.newArrayList();
        // 添加流程启动变量实例信息
        actHiVarinstList.add(getStartFlowVarInstInfo(processInstanceId));
        // 添加流程启动跳过变量实例信息
        actHiVarinstList.add(getStartSkipVarInstInfo(processInstanceId));
        // 批量保存历史变量实例
        iActHiVarinstService.saveBatch(actHiVarinstList);
        // 创建流程实例的开始字段信息
        createStartField(processInstanceId, startEvent);
        // 获取流程实例的开始活动实例
        ActHiActinst startActHiActinst = getStartActHiActinst(processInstanceId, startEvent,
                procDefId);
        // 创建流程实例的开始身份信息
        createStartIdentity(processInstanceId, userInfo);
        // 创建流程实例的开始变量实例
        createStartVarInst(processInstanceId);
        // 设置活动实例的结束时间
        startActHiActinst.setEndTime(new Date());
        // 计算活动实例的持续时间
        startActHiActinst.setDuration(startActHiActinst.getEndTime().getTime() - startActHiActinst.getStartTime().getTime());
        // 更新活动实例
        iActHiActinstService.updateById(startActHiActinst);
        // 执行流程，传入同意审核结果
        executeFlow(processInstanceId, deploymentId, AuditResultEnums.AGREE.getKey(),
                AuditResultEnums.AGREE.getMsg(), "[]");
    }

    private void createStartVarInst(String processInstanceId) {
        saveVarInst(processInstanceId,"",USER_NAME_LIST,VarTypeEnums.SERIALIZABLE.getValue());
    }

    private void createStartIdentity(String processInstanceId, UserInfoRequest userInfo) {
        ActHiIdentitylink actHiIdentitylink = new ActHiIdentitylink();
        actHiIdentitylink.setType("starter");
        actHiIdentitylink.setUserId(userInfo.getId());
        actHiIdentitylink.setCreateTime(new Date());
        actHiIdentitylink.setProcInstId(processInstanceId);
        iActHiIdentitylinkService.save(actHiIdentitylink);
    }

    private ActHiActinst getStartActHiActinst(String processInstanceId, StartEvent startEvent,
                                              String procDefId) {
        ActHiActinst startActHiActinst = new ActHiActinst();
        startActHiActinst.setRev(1);
        startActHiActinst.setProcDefId(procDefId);
        startActHiActinst.setProcInstId(processInstanceId);
        startActHiActinst.setExecutionId(processInstanceId);
        startActHiActinst.setActId(startEvent.getId());
        startActHiActinst.setActName(startEvent.getName());
        startActHiActinst.setActType(START_EVENT_TYPE);
        startActHiActinst.setStartTime(new Date());
        startActHiActinst.setEndTime(new Date());
        startActHiActinst.setDuration(startActHiActinst.getEndTime().getTime() - startActHiActinst.getStartTime().getTime());
        iActHiActinstService.save(startActHiActinst);
        return startActHiActinst;
    }

    /**
     * 创建流程结束日志
     *
     * @param processInstanceId
     */
    private void createFlowEndLog(String processInstanceId) {
        FFlowEndLogRecord fFlowEndLogRecord = new FFlowEndLogRecord();
        fFlowEndLogRecord.setPath(defaultAuditModelInfo.getPath());
        HashMap<String, String> params = new HashMap<>();
        params.put("processInstanceId", processInstanceId);
        fFlowEndLogRecord.setParams(JSON.toJSONString(params));
        fFlowEndLogRecord.setState(Boolean.TRUE);
        ifFlowEndLogRecordService.save(fFlowEndLogRecord);
    }

    private void createStartField(String processInstanceId, StartEvent startEvent) {
        FTempField fTempField = new FTempField();
        fTempField.setTempKey(processInstanceId);
        fTempField.setTempType("19");
        ifTempFieldService.save(fTempField);
        fTempField.setTempValue(fTempField.getId());
        ifTempFieldService.updateById(fTempField);
        FTempField startField = new FTempField();
        startField.setTempKey(processInstanceId + "_" + startEvent.getId());
        startField.setTempType("25");
        startField.setTempValue("[]");
        ifTempFieldService.save(startField);
    }

    private ActHiVarinst getStartFlowVarInstInfo(String processInstanceId) {
        final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED";
        Date now = new Date();
        ActHiVarinst flow = new ActHiVarinst();
        flow.setRev(0);
        flow.setProcInstId(processInstanceId);
        flow.setExecutionId(processInstanceId);
        flow.setName(FLOWABLE_SKIP_EXPRESSION_ENABLED);
        flow.setVarType(VarTypeEnums.BOOLEAN.getValue());
        flow.setLong_(1L);
        flow.setCreateTime(now);
        flow.setLastUpdatedTime(now);
        return flow;
    }

    private ActHiVarinst getStartSkipVarInstInfo(String processInstanceId) {
        Date now = new Date();
        ActHiVarinst skip = new ActHiVarinst();
        skip.setRev(0);
        skip.setProcInstId(processInstanceId);
        skip.setExecutionId(processInstanceId);
        skip.setName("skip");
        skip.setVarType(VarTypeEnums.STRING.getValue());
        skip.setText("startUser");
        skip.setCreateTime(now);
        skip.setLastUpdatedTime(now);
        return skip;
    }

    private void createEstProcessStartRecord(String bizId, String bizName, String bizStatus,
                                             String modelName, String processInstanceId) {
        UserInfoRequest userInfo = BaseContext.getUserInfo();
        EstProcessStartRecord processStartRecord = new EstProcessStartRecord();
        processStartRecord.setBizId(bizId);
        processStartRecord.setBizName(bizName);
        processStartRecord.setBizStatus(bizStatus);
        processStartRecord.setDataId(bizId);
        processStartRecord.setDataType("50");
        processStartRecord.setStartType("0");
        processStartRecord.setProcessInstanceId(processInstanceId);
        processStartRecord.setProcessInstanceCode(getProcessInstanceCode());
        processStartRecord.setTenantId(userInfo.getTenantId());
        processStartRecord.setProcessStartName("发起审批" + DateUtils.format(new Date(), DateFormatPattern.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getPattern()));
        processStartRecord.setModuleName(modelName);
        iEstProcessStartRecordService.save(processStartRecord);
    }

    private ActHiProcinst createActHiProcinst(ActReProcdef proInfo,
                                              UserInfoRequest userInfo,
                                              Process process) throws Exception {
        String proInfoId = proInfo.getId();
        String processInstanceId = UUID.randomUUID().toString();
        ActHiProcinst actHiProcinst = new ActHiProcinst();
        actHiProcinst.setId(processInstanceId);
        actHiProcinst.setProcInstId(processInstanceId);
        actHiProcinst.setProcDefId(proInfoId);
        actHiProcinst.setRev(6);
        actHiProcinst.setBusinessKey("[]");
        actHiProcinst.setStartTime(new Date());
        actHiProcinst.setStartUserId(userInfo.getId());
        actHiProcinst.setName(userInfo.getRealName() + ConvertUtil.getFormatDateGMT(new Date(),
                DateFormatPattern.YEAR_MONTH_DAY.getPattern()));
        StartEvent startEvent = process.getStartEvent();
        if (Objects.isNull(startEvent)) {
            throw new AuditException("流程配置无开始节点");
        }
        String startEventId = startEvent.getId();
        actHiProcinst.setStartActId(startEventId);
        iActHiProcinstService.save(actHiProcinst);
        return actHiProcinst;
    }

    private String getProcessInstanceCode() {
        String year = DateUtils.format(new Date(), "yyyy");
        long incr = redisCache.incr(PROCESS_INSTANCE_CODE + year);
        return PROCESS_INSTANCE_CODE_PREFIX + year + String.format("%08d", incr);
    }

    /**
     * 检查模型数据的完整性
     * 此方法旨在确保流程定义中的各个关键元素不为空，以满足流程引擎处理的基本要求
     * 如果任何一个关键元素为空，则抛出AuditException异常，以提示具体的错误信息
     *
     * @param processDefinition 流程定义对象，包含了流程的所有相关信息
     * @throws AuditException 如果流程定义为空，或者流程中的关键元素（如开始节点、结束节点、用户任务等）为空，则抛出此异常
     */
    private void checkModelData(ProcessDefinition processDefinition) throws AuditException {
        // 检查流程定义对象是否为空
        if (Objects.isNull(processDefinition)) {
            throw new AuditException("解析流程{xml}信息错误");
        }
        // 获取流程对象
        Process process = processDefinition.getProcess();
        // 检查流程对象是否为空
        if (Objects.isNull(process)) {
            throw new AuditException("流程配置信息错误");
        }
        // 获取流程中的序列流列表
        List<SequenceFlow> flowList = process.getSequenceFlow();
        // 检查序列流列表是否为空
        if (CollectionUtils.isEmpty(flowList)) {
            throw new AuditException("流程xml信息不能为空");
        }
        // 获取流程的开始事件
        StartEvent startEvent = process.getStartEvent();
        // 检查开始事件是否为空
        if (Objects.isNull(startEvent)) {
            throw new AuditException("流程xml开始节点信息异常");
        }
        // 获取流程的结束事件
        EndEvent endEvent = process.getEndEvent();
        // 检查结束事件是否为空
        if (Objects.isNull(endEvent)) {
            throw new AuditException("流程xml结束节点信息异常");
        }
        // 获取流程中的用户任务列表
        List<UserTask> userTaskList = process.getUserTask();
        // 检查用户任务列表是否为空
        if (CollectionUtils.isEmpty(userTaskList)) {
            throw new AuditException("流程xml信息用户不能为空");
        }
    }


    private FModelSchemeInfo getModelSchemeInfoByDeploymentId(String deploymentId) throws AuditException {
        FModelSchemeInfo modelSchemeInfo = ifModelSchemeInfoService.getByDeploymentId(deploymentId);
        if (Objects.isNull(modelSchemeInfo)) {
            throw new AuditException("审核方案不存在");
        }
        return modelSchemeInfo;
    }

    /**
     * 获取审核方案
     *
     * @param id
     * @return
     * @throws AuditException
     */
    private FModelSchemeInfo getModelSchemeInfo(String id) throws AuditException {
        String key = MODEL_SCHEME_INFO + id;
        String value = redisCache.getString(key);
        if (StringUtils.isNotBlank(value)) {
            return JSON.parseObject(value, FModelSchemeInfo.class);
        }
        FModelSchemeInfo modelSchemeInfo = ifModelSchemeInfoService.getById(id);
        if (Objects.isNull(modelSchemeInfo)) {
            throw new AuditException("审核方案不存在");
        }
        redisCache.setString(key, JSON.toJSONString(modelSchemeInfo), 60 * 5);
        return modelSchemeInfo;
    }

    /**
     * 获取审核方案
     *
     * @param id
     * @return
     * @throws AuditException
     */
    private FModelScheme getModelScheme(String id) throws AuditException {
        String key = MODEL_SCHEME + id;
        String value = redisCache.getString(key);
        if (StringUtils.isNotBlank(value)) {
            return JSON.parseObject(value, FModelScheme.class);
        }
        FModelScheme modelScheme = ifModelSchemeService.getById(id);
        if (Objects.isNull(modelScheme)) {
            throw new AuditException("审核方案{xml,json}不存在");
        }
        redisCache.setString(key, JSON.toJSONString(modelScheme), 60 * 5);
        return modelScheme;
    }

    private ActReProcdef getProInfo(String deploymentId) throws AuditException {
        ActReProcdef actReProcdef = iActReProcdefService.getByDeploymentId(deploymentId);
        if (Objects.isNull(actReProcdef)) {
            throw new AuditException("流程方案版本信息不存在");
        }
        return actReProcdef;
    }

    /**
     * 将json转换为对象
     *
     * @param modelJson
     * @return
     */
    private AuditTemplateConfig modelJsonToObject(String modelJson) {
        return JSON.parseObject(modelJson, AuditTemplateConfig.class);
    }

    /**
     * 将xml转换为对象
     *
     * @param modelXml
     * @return
     * @throws Exception
     */
    private ProcessDefinition modelXmlToObject(String modelXml) throws Exception {
        return JSON.parseObject(XmlToJsonConverter.convertToJson(modelXml),
                ProcessDefinition.class);
    }
}
